﻿// =====================================================================
//  File:		CRMWebAPI
//  Summary:	Helper library for working with CRM Web API
// =====================================================================
// 
//  THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
//  PARTICULAR PURPOSE.
// 
//  Any use or other rights related to this source code, resulting object code or 
//  related artifacts are controlled the prevailing EULA in effect. See the EULA
//  for detail rights. In the event no EULA was provided contact copyright holder
//  for a current copy.
//
// =====================================================================
using Microsoft.Crm.Sdk.Samples.HelperCode;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Dynamic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Reflection;
using System.Runtime.Caching;
using System.Text;
using System.Threading.Tasks;
using isky.webapi.requests;
using isky.webapi;

namespace isky.webapi
{
    public partial class CRMWebAPI
    {
        private HttpClient _httpClient = null;
        private string _apiUrl = string.Empty;
        private string _AccessToken = string.Empty;
        private string _RefreshToken = string.Empty;
        private Func<string, string, Task<string>> _getAccessToken = null;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="apiUrl">CRM API base URL e.g. https://orgname.api.crm.dynamics.com/api/data/v8.0/ </param>
        /// <param name="accessToken">allows for hard coded access token for testing</param>
        /// <param name="callerID">user id to impersonate on calls</param>
        /// <param name="getAccessToken">method to call to refresh access token, called before each use of token</param>
        public CRMWebAPI(string apiUrl, string accessToken, Guid callerID = default(Guid), Func<string, string, Task<string>> getAccessToken = null)
        {
            _apiUrl = apiUrl;
            _httpClient = new HttpClient();
            _AccessToken = accessToken;
            _getAccessToken = getAccessToken;
            _httpClient.DefaultRequestHeaders.Authorization =
               new AuthenticationHeaderValue("Bearer", accessToken);
            SetHttpClientDefaults(callerID);
        }

        public CRMWebAPI(FileConfiguration config, Guid callerID = default(Guid))
        {
            _apiUrl = string.Concat(config.ServiceUrl, "api/data/v8.0/");
            Authentication auth = new Authentication(config);
            _httpClient = new HttpClient(auth.ClientHandler, true);
            _httpClient.Timeout = new TimeSpan(0, 2, 0);
            SetHttpClientDefaults(callerID);
            //ObjectCache oCache = MemoryCache.Default;
            //if (oCache["CrmAuthToken"] as CrmAuth != null)
            //{
            //    var crmAuth = oCache["CrmAuthToken"] as CrmAuth;
            //    _AccessToken = crmAuth.access_token;
            //    _getAccessToken = CrmAuth.CrmRefreshTokenAsync;
            //    _RefreshToken = crmAuth.refresh_token;
            // }
        }

        public CRMWebAPI(string apiUrl, HttpClient httpClient, Guid callerID = default(Guid))
        {
            _apiUrl = apiUrl;
            _httpClient = httpClient;
            SetHttpClientDefaults(callerID);
        }

        /// <summary>
        /// On-premise Active Directory with Credentials
        /// </summary>
        /// <param name="apiUrl"></param>
        /// <param name="networkCredential"></param>
        public CRMWebAPI(string apiUrl, NetworkCredential networkCredential = null, Guid callerID = default(Guid))
        {
            _apiUrl = apiUrl;

            if (networkCredential != null)
                _httpClient = new HttpClient(new HttpClientHandler() { Credentials = networkCredential });
            else
                _httpClient = new HttpClient();

            SetHttpClientDefaults(callerID);
        }
        /// <summary>
        /// Retrieve a list of records based on query options
        /// </summary>
        /// <param name="uri">e.g. accounts</param>
        /// <param name="QueryOptions">Filter, OrderBy,Select, and other options</param>
        /// <returns></returns>


        public async Task<CRMBatchResult> BatchOperation(Action<MultipartContent, string> ContentAction, HttpClient httpClient, string[] entityCollectionNames, Guid callerID = default(Guid))
        {
            httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
            httpClient.DefaultRequestHeaders.Add("OData-MaxVersion", "4.0");
            httpClient.DefaultRequestHeaders.Add("OData-Version", "4.0");

            if (callerID != Guid.Empty)
                httpClient.DefaultRequestHeaders.Add("MSCRMCallerID", callerID.ToString());

            httpClient.Timeout = new TimeSpan(0, 2, 0);
            var batchid = "batch_" + Guid.NewGuid().ToString();

            MultipartContent batchContent = new MultipartContent("mixed", batchid);

            ContentAction(batchContent, _apiUrl);

            if (batchContent.Count() == 0) return null;



            HttpRequestMessage batchRequest = new HttpRequestMessage(HttpMethod.Post, _apiUrl + "$batch");

            batchRequest.Content = batchContent;

            var batchstring = await batchRequest.Content.ReadAsStringAsync();

            var response = await httpClient.SendAsync(batchRequest);


            CRMBatchResult finalResult = new CRMBatchResult();
            finalResult.ResultItems = new List<CRMBatchResultItem>();

            var responseString = response.Content.ReadAsStringAsync();
            MultipartMemoryStreamProvider batchStream = await response.Content.ReadAsMultipartAsync();


            //batchStream.Contents.ToList().ForEach(async changesetStream =>
            foreach (var changesetStream in batchStream.Contents)
            {
                StreamContent changesetFixedContent = FixupChangeStreamDueToBug(changesetStream);

                var changesetFixedStream = await changesetFixedContent.ReadAsMultipartAsync();


                foreach (var responseContent in changesetFixedStream.Contents)
                {
                    var fixedREsponseContent = FixupToAddCorrectHttpContentType(responseContent);
                    var individualResponseString = await fixedREsponseContent.ReadAsStringAsync();
                    var indivdualResponse = await fixedREsponseContent.ReadAsHttpResponseMessageAsync();

                    if (!indivdualResponse.IsSuccessStatusCode)
                    {
                        //var jsonData = JsonConvert.SerializeObject(datalist);
                        EnsureSuccessStatusCode(indivdualResponse);
                    }
                    var idString = indivdualResponse.Headers.GetValues("OData-EntityId").FirstOrDefault();
                    //var entityCollectionNames = cRMEntities.Select(t => t.entityCollectionName).ToArray();
                    foreach (var entityCollection in entityCollectionNames)
                    {
                        if (!idString.Contains(entityCollection)) continue;
                        idString = idString.Replace(_apiUrl + entityCollection, "").Replace("(", "").Replace(")", "");
                        CRMBatchResultItem resultItem = new CRMBatchResultItem();

                        //if alternate key was used to perform an upsert, guid not currently returned
                        //the call returns the alternate key which is not in guid format
                        var idGuid = Guid.Empty;
                        if (Guid.TryParse(idString, out idGuid))
                        {
                            resultItem.EntityID = idGuid;
                        }
                        else
                        {
                            resultItem.ElternateKey = idString;
                        }
                        resultItem.EntityLogicName = entityCollection;
                        finalResult.ResultItems.Add(resultItem);
                    }

                }
            }


            return finalResult;
        }




        /// <summary>
        /// create a new record
        /// </summary>
        /// <param name="entityCollection"></param>
        /// <param name="data"></param>
        /// <returns></returns>
        public async Task<Guid> Create(string entityCollection, object data)
        {
            await CheckAuthToken();

            var fullUrl = _apiUrl + entityCollection;

            HttpRequestMessage request = new HttpRequestMessage(new HttpMethod("Post"), fullUrl);

            var jsonData = JsonConvert.SerializeObject(data);

            request.Content = new StringContent(jsonData, Encoding.UTF8, "application/json");

            var response = await _httpClient.SendAsync(request);

            EnsureSuccessStatusCode(response, jsonData: jsonData);

            Guid idGuid = GetEntityIDFromResponse(fullUrl, response);

            return idGuid;
        }


        /// <summary>
        /// Update or Insert based on a match with the entityID
        /// </summary>
        /// <param name="entityCollection"></param>
        /// <param name="entityID"></param>
        /// <param name="data"></param>
        /// <param name="preventCreate"></param>
        /// <param name="preventUpdate"></param>
        /// <returns></returns>
        public async Task<CRMUpdateResult> Update(string entityCollection, Guid entityID, object data, bool Upsert = true)
        {
            return await Update(entityCollection, entityID.ToString(), data, Upsert);
        }
        /// <summary>
        /// Update or insert based on match with key provided in form of Field = Value
        /// </summary>
        /// <param name="entityCollection"></param>
        /// <param name="key"></param>
        /// <param name="data"></param>
        /// <param name="preventCreate"></param>
        /// <param name="preventUpdate"></param>
        /// <returns></returns>
        public async Task<CRMUpdateResult> Update(string entityCollection, string key, object data, bool Upsert = true)
        {
            await CheckAuthToken();
            CRMUpdateResult result = new CRMUpdateResult();
            var fullUrl = _apiUrl + entityCollection;

            HttpRequestMessage request = new HttpRequestMessage(new HttpMethod("PATCH"), fullUrl + "(" + key + ")");

            var jsonData = JsonConvert.SerializeObject(data);

            request.Content = new StringContent(jsonData, Encoding.UTF8, "application/json");

            if (!Upsert)
                request.Headers.Add("If-Match", "*");

            var response = await _httpClient.SendAsync(request);

            result.EntityID = GetEntityIDFromResponse(fullUrl, response);


            if (!response.IsSuccessStatusCode)
            {
                if ((response.StatusCode == HttpStatusCode.PreconditionFailed) &&
                        (!Upsert))
                {
                    return result;
                }
                EnsureSuccessStatusCode(response, jsonData: jsonData);
            }

            return result;

        }

        /// <summary>
        /// Optimistic Lock
        /// </summary>
        /// <param name="entityCollection">实体复数名</param>
        /// <param name="key">记录主键</param>
        /// <param name="data">记录属性</param>
        /// <param name="etag">记录版本</param>
        /// <returns></returns>
        public async Task<CRMUpdateResult> UpdateIfMatch(string entityCollection, Guid key, object data, string etag)
        {
            CRMUpdateResult result = new CRMUpdateResult();
            var fullUrl = _apiUrl + entityCollection;

            HttpRequestMessage request = new HttpRequestMessage(new HttpMethod("PATCH"), fullUrl + "(" + key.ToString() + ")");

            var jsonData = JsonConvert.SerializeObject(data);

            request.Content = new StringContent(jsonData, Encoding.UTF8, "application/json");

            request.Headers.Add("If-Match", etag);

            var response = await _httpClient.SendAsync(request);

            result.EntityID = GetEntityIDFromResponse(fullUrl, response);


            if (!response.IsSuccessStatusCode)
            {
                if (response.StatusCode == HttpStatusCode.PreconditionFailed)
                {
                    return result;
                }
                EnsureSuccessStatusCode(response, jsonData: jsonData);
            }

            return result;

        }

        /// <summary>
        /// delete record
        /// </summary>
        /// <param name="entityCollection"></param>
        /// <param name="entityID"></param>
        /// <returns></returns>
        public async Task Delete(string entityCollection, Guid entityID)
        {
            await CheckAuthToken();

            var response = await _httpClient.DeleteAsync(_apiUrl + entityCollection + "(" + entityID.ToString() + ")");

            EnsureSuccessStatusCode(response);

        }

    }
}
